
package com.gitee.flow.service;

import com.gitee.flow.FlowMapper;
import com.gitee.flow.dao.HiTaskinstDao;
import com.gitee.flow.util.FlowablePageUtil;
import com.gitee.jmash.common.grpc.GrpcContext;
import com.gitee.jmash.core.orm.DtoPageImpl;
import com.gitee.jmash.core.orm.DtoTotal;
import com.gitee.jmash.core.orm.cdi.JpaTenantService;
import com.gitee.jmash.core.orm.jpa.TenantEntityManager;
import com.gitee.jmash.core.orm.tenant.TenantService;
import com.gitee.jmash.core.transaction.JakartaTransaction;
import com.gitee.jmash.core.utils.FilePathUtil;
import jakarta.enterprise.inject.Typed;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.transaction.Transactional;
import jakarta.transaction.Transactional.TxType;
import jakarta.validation.ValidationException;
import jakarta.validation.executable.ValidateOnExecution;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jmash.flow.protobuf.HiCommentModel;
import jmash.flow.protobuf.HiProcInstReq;
import jmash.flow.protobuf.HiProcinstKey;
import jmash.flow.protobuf.IdentityLinkType;
import jmash.flow.protobuf.ProcDefReq;
import jmash.flow.protobuf.ProcInstReq;
import jmash.flow.protobuf.TaskAttachmentReq;
import jmash.flow.protobuf.TaskDefList;
import jmash.flow.protobuf.TaskDefModel;
import jmash.flow.protobuf.TaskIdentityLinkReq;
import jmash.flow.protobuf.TaskModel;
import jmash.flow.protobuf.TaskPage;
import jmash.flow.protobuf.TaskReq;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.flowable.bpmn.model.BpmnModel;
import org.flowable.bpmn.model.FlowElement;
import org.flowable.bpmn.model.Gateway;
import org.flowable.bpmn.model.UserTask;
import org.flowable.engine.HistoryService;
import org.flowable.engine.IdentityService;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.ProcessInstanceQueryProperty;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.repository.ProcessDefinitionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.runtime.ProcessInstanceQuery;
import org.flowable.engine.task.Attachment;
import org.flowable.engine.task.Comment;
import org.flowable.identitylink.api.IdentityLink;
import org.flowable.image.impl.DefaultProcessDiagramGenerator;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.task.api.history.HistoricTaskInstance;
import org.flowable.task.api.history.HistoricTaskInstanceQuery;
import org.flowable.task.service.impl.TaskQueryProperty;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.flowable.variable.api.history.HistoricVariableInstanceQuery;

/**
 * Flow读服务.
 *
 * @author <a href="mailto:service@crenjoy.com">crenjoy</a>
 */
@Typed(FlowRead.class)
@Transactional(TxType.SUPPORTS)
@JpaTenantService
@ValidateOnExecution
public class FlowReadBean implements FlowRead, JakartaTransaction {

  protected TenantEntityManager tem = new TenantEntityManager();

  protected  HiTaskinstDao hiTaskinstDao = new HiTaskinstDao(tem);

  @Inject
  RepositoryService repositoryService;

  @Inject
  IdentityService identityService;

  @Inject
  RuntimeService runtimeService;

  @Inject
  TaskService taskService;

  @Inject
  HistoryService historyService;

  @PersistenceContext(unitName = "ReadFlow")
  public void setEntityManager(EntityManager entityManager) {
    this.tem.setEntityManager(entityManager, false);
  }

  @Override
  public EntityManager getEntityManager() {
    return this.tem.getEntityManager();
  }

  @Override
  @SuppressWarnings("unchecked")
  public <T extends TenantService> T setTenant(String tenant) {
    this.tem.setTenant(tenant);
    return (T) this;
  }

  @Override
  public void setTenantOnly(String tenant) {
    this.tem.setTenantOnly(tenant);
  }

  @Override
  public String getTenant() {
    return this.tem.getTenant();
  }

  /**
   * 设置认证用户ID.
   */
  public String setAuthenticatedUserId() {
    // 用户ID
    String userId = GrpcContext.USER_TOKEN.get().getName();
    identityService.setAuthenticatedUserId(userId);
    return userId;
  }


  @Override
  public List<ProcessDefinition> findProcDefList(ProcDefReq req) {
    ProcessDefinitionQuery query = repositoryService.createProcessDefinitionQuery();
    query.processDefinitionTenantId(req.getTenant());
    if (StringUtils.isNotBlank(req.getName())) {
      query = query.processDefinitionName(req.getName());
    }
    if (StringUtils.isNotBlank(req.getKey())) {
      query = query.processDefinitionKey(req.getKey());
    }
    if (StringUtils.isNotBlank(req.getCategory())) {
      query = query.processDefinitionCategory(req.getCategory());
    }
    if (StringUtils.isNotBlank(req.getParentDeploymentId())) {
      query = query.parentDeploymentId(req.getParentDeploymentId());
    }
    return query.orderByProcessDefinitionKey().asc().orderByProcessDefinitionVersion().desc()
        .list();
  }

  @Override
  public ProcessDefinition findProcDefByKey(String key) {
    ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
        .processDefinitionKey(key).latestVersion().singleResult();
    return procDef;
  }

  @Override
  public ProcessDefinition findProcDefById(String procDefId) {
    return repositoryService.getProcessDefinition(procDefId);
  }

  @Override
  public String findProcDefDiagram(String procDefId) throws FileNotFoundException, IOException {
    ProcessDefinition pd = repositoryService.getProcessDefinition(procDefId);
    if (pd == null) {
      throw new ValidationException("流程不存在");
    }
    String fileSrc = String.format("flow/%s/%s.png", pd.getKey(), pd.getId());
    String realFileSrc = FilePathUtil.realPath(fileSrc,true);
    if (new File(realFileSrc).exists()) {
      return realFileSrc;
    }

    // 获取最新的流程定义
    BpmnModel bpmnModel = repositoryService.getBpmnModel(procDefId);

    // 生成流程图
    InputStream processDiagram =
        new DefaultProcessDiagramGenerator().generatePngDiagram(bpmnModel, 1.0, false);

    IOUtils.copy(processDiagram, new FileOutputStream(realFileSrc));
    return realFileSrc;
  }

  @Override
  public String findProcDefXmlById(String procDefId) throws IOException {
    InputStream inputStream = repositoryService.getProcessModel(procDefId);
    String xml = IOUtils.toString(inputStream, "UTF-8");
    return xml;
  }

  @Override
  public DtoPageImpl<ProcessInstance, DtoTotal> findProcInstPage(ProcInstReq req) {
    ProcessInstanceQuery query = runtimeService.createProcessInstanceQuery();
    if (!req.getPlatform()) {
      query.processInstanceTenantId(req.getTenant());
    }
    if (StringUtils.isNotBlank(req.getCategory())) {
      query.processDefinitionCategory(req.getCategory());
    }
    if (StringUtils.isNotBlank(req.getProcDefId())) {
      query.processDefinitionId(req.getProcDefId());
    }
    if (StringUtils.isNotBlank(req.getProcDefKey())) {
      query.processDefinitionKey(req.getProcDefKey());
    }
    if (StringUtils.isNotBlank(req.getUserId())) {
      query.startedBy(req.getUserId());
    }
    if (StringUtils.isNotBlank(req.getInvolvedUser())) {
      query.involvedUser(req.getInvolvedUser());
    }
    if (StringUtils.isNotBlank(req.getBusinessStatus())) {
      query.processInstanceBusinessStatus(req.getBusinessStatus());
    }
    if (StringUtils.isNotBlank(req.getCategory())){
      query.processDefinitionCategory(req.getCategory());
    }
    if (StringUtils.isNotBlank(req.getOrderName())) {
      query.orderBy(ProcessInstanceQueryProperty.findByName(req.getOrderName()));
      query = req.getOrderAsc() ? query.asc() : query.desc();
    } else {
      query.orderByStartTime().desc();
    }
    return FlowablePageUtil.listPage(req.getCurPage(), req.getPageSize(), query);
  }


  @Override
  public DtoPageImpl<HistoricProcessInstance, DtoTotal> findHiProcInstPage(HiProcInstReq req) {
    HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();
    if (!req.getPlatform()) {
      query.processInstanceTenantId(req.getTenant());
    }
    if (StringUtils.isNotBlank(req.getCategory())) {
      query.processDefinitionCategory(req.getCategory());
    }
    if (StringUtils.isNotBlank(req.getProcDefId())) {
      query.processDefinitionId(req.getProcDefId());
    }
    if (StringUtils.isNotBlank(req.getProcDefKey())) {
      query.processDefinitionKey(req.getProcDefKey());
    }
    if (StringUtils.isNotBlank(req.getUserId())) {
      query.startedBy(req.getUserId());
    }
    if (StringUtils.isNotBlank(req.getInvolvedUser())) {
      query.involvedUser(req.getInvolvedUser());
    }
    if (StringUtils.isNotBlank(req.getBusinessStatus())) {
      query.processInstanceBusinessStatus(req.getBusinessStatus());
    }
    if (StringUtils.isNotBlank(req.getCategory())){
      query.processDefinitionCategory(req.getCategory());
    }
    if (!req.getUnfinish()) {
      query.finished();
    }
    if (StringUtils.isNotBlank(req.getOrderName())) {
      query.orderBy(ProcessInstanceQueryProperty.findByName(req.getOrderName()));
      query = req.getOrderAsc() ? query.asc() : query.desc();
    } else {
      query.orderByProcessInstanceEndTime().desc();
    }
    return FlowablePageUtil.listPage(req.getCurPage(), req.getPageSize(), query);
  }

  @Override
  public DtoPageImpl<Task, DtoTotal> findTaskPage(TaskReq req) {
    TaskQuery query = taskService.createTaskQuery();
    if (!req.getPlatform()) {
      query.taskTenantId(req.getTenant());
    }
    if (StringUtils.isNotBlank(req.getProcDefId())) {
      query.processDefinitionId(req.getProcDefId());
    }
    if (StringUtils.isNotBlank(req.getProcInstId())) {
      query.processInstanceId(req.getProcInstId());
    }
    if (StringUtils.isNotBlank(req.getAssigneeUser())) {
      query.taskAssignee(req.getAssigneeUser());
    }
    if (StringUtils.isNotBlank(req.getCandidateUser())) {
      query.taskCandidateUser(req.getCandidateUser());
      // 忽略已分配用户的候选.
      query.ignoreAssigneeValue();
    }
    if (StringUtils.isNotBlank(req.getCandidateGroup())) {
      query.taskCandidateGroup(req.getCandidateGroup());
    }
    if (StringUtils.isNotBlank(req.getBusinessKey())) {
      query.processInstanceBusinessKey(req.getBusinessKey());
    }
    if (req.getHasPriority()){
      query.taskPriority(req.getPriorityValue() * 50);
    }
    if (req.getHasAssigned()) {
      if (req.getAssigned()) {
        query.taskAssigned();
      } else {
        query.taskUnassigned();
      }
    }
    if (StringUtils.isNotBlank(req.getCategory())){
      query.taskCategory(req.getCategory());
    }
    if(StringUtils.isNotBlank(req.getProcDefKey())){
      query.processDefinitionKey(req.getProcDefKey());
    }
    query.active();
    if (StringUtils.isNotBlank(req.getOrderName())) {
      query.orderBy(TaskQueryProperty.findByName(req.getOrderName()));
      query = req.getOrderAsc() ? query.asc() : query.desc();
    } else {
      query.orderByTaskCreateTime().desc();
      query.orderByTaskPriority().desc();
    }
    return FlowablePageUtil.listPage(req.getCurPage(), req.getPageSize(), query);
  }

  @Override
  public TaskPage findTaskModelPage(TaskReq request) {
    DtoPageImpl<Task, DtoTotal> page = findTaskPage(request);
    TaskPage.Builder builder =
        TaskPage.newBuilder().setCurPage(page.getCurPage()).setPageSize(page.getPageSize());
    builder.setTotalSize(Long.valueOf(page.getTotalSize()).intValue());
    Map<String, ProcessInstance> map = new HashMap<>();
    for (Task task : page.getResults()) {
      if (!map.containsKey(task.getProcessInstanceId())) {
        ProcessInstance procInst = runtimeService.createProcessInstanceQuery()
            .processInstanceId(task.getProcessInstanceId()).singleResult();
        map.put(task.getProcessInstanceId(), procInst);

      }
      ProcessInstance procInst = map.get(task.getProcessInstanceId());
      builder.addResults(FlowMapper.INSTANCE.modelTask(task, procInst));
    }
    return builder.build();
  }

  @Override
  public TaskModel findTaskModelById(String taskId) {
    TaskQuery query = taskService.createTaskQuery();
    query.taskTenantId(getTenant());
    Task task = query.taskId(taskId).singleResult();
    ProcessInstance procInst = runtimeService.createProcessInstanceQuery()
        .processInstanceId(task.getProcessInstanceId()).singleResult();
    return FlowMapper.INSTANCE.modelTask(task, procInst);
  }

  protected Task findTaskById(String taskId) {
    TaskQuery query = taskService.createTaskQuery();
    query.taskTenantId(getTenant());
    return query.taskId(taskId).singleResult();
  }

  /**
   * 获取历史任务信息.
   */
  protected HistoricTaskInstance findHistoryTaskById(String taskId) {
    HistoricTaskInstanceQuery query = historyService.createHistoricTaskInstanceQuery();
    query.taskTenantId(getTenant());
    return query.taskId(taskId).singleResult();
  }

  @Override
  public List<HiCommentModel> findHiCommentList(String procInstId) {
    List<Comment> comments = taskService.getProcessInstanceComments(procInstId);
    List<HiCommentModel> list = FlowMapper.INSTANCE.listComment((List) comments);
    if (null == list) {
      return list;
    }
    List<HiCommentModel> result = new ArrayList<>();
    for (HiCommentModel comment : list) {
      HistoricTaskInstance task = findHistoryTaskById(comment.getTaskId());
      String assignee = comment.getUserId();
      if (null != task.getAssignee()) {
        assignee = task.getAssignee();
      }
      comment = comment.toBuilder().setTaskName(task.getName()).setAssignee(assignee)
          .build();
      result.add(comment);
    }
    return result;
  }

  @Override
  public ProcessInstance findProcInst(String procInstId) {
    ProcessInstance procInst =
        runtimeService.createProcessInstanceQuery().processInstanceId(procInstId).singleResult();
    return procInst;
  }

  @Override
  public Map<String, Object> findProcInstVars(String executionId) {
    return runtimeService.getVariables(executionId);
  }

  @Override
  public String findProcInstDiagram(String procInstId) throws FileNotFoundException, IOException {
    // 获取流程实例.
    ProcessInstance procInst =
        runtimeService.createProcessInstanceQuery().processInstanceId(procInstId).singleResult();
    // 流程实例活动节点.
    List<String> activeActivityIds = runtimeService.getActiveActivityIds(procInstId);
    // 获取最新的流程定义
    BpmnModel bpmnModel = repositoryService.getBpmnModel(procInst.getProcessDefinitionId());

    // 生成流程图（暂时不绘制高亮线.）
    InputStream processDiagram = new DefaultProcessDiagramGenerator().generateDiagram(bpmnModel,
        "png", activeActivityIds, false);

    String fileSrc = FilePathUtil.createNewTempFile(procInstId + ".png", "flow_inst");
    String realFileSrc = FilePathUtil.realPath(fileSrc,true);
    IOUtils.copy(processDiagram, new FileOutputStream(realFileSrc));
    return realFileSrc;
  }

  @Override
  public TaskDefList findTaskDefAllowGoback(String taskId) {
    // 获取当前任务
    Task currentTask = taskService.createTaskQuery().taskId(taskId).singleResult();
    if (currentTask == null) {
      throw new IllegalArgumentException("Task not found with id: " + taskId);
    }
    BpmnModel bpmnModel = repositoryService.getBpmnModel(currentTask.getProcessDefinitionId());
    // 从历史任务开始逆向遍历，直到找到网关或开始事件
    HistoricTaskInstance prevTask =
        findPreviousTask(currentTask.getExecutionId(), currentTask.getCreateTime());
    TaskDefList.Builder availableBackwardTasks = TaskDefList.newBuilder();

    while (prevTask != null) {
      FlowElement flowElement = bpmnModel.getFlowElement(prevTask.getTaskDefinitionKey());
      if (flowElement instanceof UserTask) {
        TaskDefModel model = TaskDefModel.newBuilder().setTaskName(prevTask.getName())
            .setTaskDefKey(prevTask.getTaskDefinitionKey())
            .setCompletedBy(prevTask.getCompletedBy()).build();
        availableBackwardTasks.addResults(model);
      } else if (!(flowElement instanceof Gateway)) {
        // 如果不是网关，则停止遍历
        break;
      }

      // 继续查找前一个任务
      prevTask = findPreviousTask(prevTask.getExecutionId(), prevTask.getCreateTime());
    }
    return availableBackwardTasks.build();
  }

  // 获取前一个历史任务实例.
  private HistoricTaskInstance findPreviousTask(String executionId, Date afterEndTime) {
    List<HistoricTaskInstance> historicTasks = historyService.createHistoricTaskInstanceQuery()
        .executionId(executionId).finished().or().taskCompletedBefore(afterEndTime).endOr()
        .orderByHistoricTaskInstanceEndTime().desc().listPage(0, 1);
    return historicTasks.isEmpty() ? null : historicTasks.get(0);
  }

  @Override
  public List<Attachment> findTaskAttachmentByReq(TaskAttachmentReq req) {
    if (StringUtils.isNotBlank(req.getProcInstId())) {
      return taskService.getProcessInstanceAttachments(req.getProcInstId());
    }
    return taskService.getTaskAttachments(req.getTaskId());
  }

  @Override
  public List<IdentityLink> findTaskIdentityLinkByReq(TaskIdentityLinkReq req) {
    List<IdentityLink> identityLinks = taskService.getIdentityLinksForTask(req.getTaskId());
    if (IdentityLinkType.all.equals(req.getType())) {
      return identityLinks;
    }
    List<IdentityLink> typeUser = new ArrayList<>();
    for (IdentityLink user : identityLinks) {
      if (req.getType().name().equals(user.getType())) {
        typeUser.add(user);
      }
    }
    return typeUser;
  }

  @Override
  public HistoricProcessInstance findHiProcInstById(HiProcinstKey req) {
    HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();
    query.processInstanceTenantId(getTenant());

    return query.processInstanceId(req.getId()).singleResult();
  }

  @Override
  public Map<String,Object> findHiProcVars(String procInstId) {
    HistoricVariableInstanceQuery query = historyService.createHistoricVariableInstanceQuery();
    List<HistoricVariableInstance> vars = query.processInstanceId(procInstId).list();
    if(vars==null||vars.size()==0){
      return null;
    }
    Map<String,Object> map = new HashMap<>();
    for(HistoricVariableInstance var : vars ) {
      map.put(var.getVariableName(), var.getValue());
    }
    return map;
  }

  @Override
  public void close() throws Exception {
    CDI.current().destroy(this);
  }
}
